import pandas as pd
import Core.Label as Label
import Core.Gadget as Gadget
import numpy as np
import Core.Portfolio as Portfoio
import Core.DataSeries as DataSeries
import Core.Algorithm as Algorithm
import Core.IO as IO
import datetime
import math
from Factors.General import *


def CalcMarketStyle(database, instruments, datetime1, datetime2):
    print("Calc MarketStyle")
    outputFolder = "D:/Data/FamaFrench_MarketStyle/"

    # ---Prepare 6 Portfolio---
    #portfolios = {}
    #portfolios["SmaValue"] = Portfoio.Portfolio("SmaValue", database)
    #portfolios["BigValue"] = Portfoio.Portfolio("BigValue", database)
    #portfolios["SmaNeutral"] = Portfoio.Portfolio("SmaNeutral", database)
    #portfolios["BigNeutral"] = Portfoio.Portfolio("BigNeutral", database)
    #portfolios["SmaGrowth"] = Portfoio.Portfolio("SmaGrowth", database)
    #portfolios["BigGrowth"] = Portfoio.Portfolio("BigGrowth", database)

    #for name, portfolio in portfolios.items():
    #   portfolio.Deposit(1000*10000, datetime1)

    # ---Loop DateTimes---
    # ---update by Month---
    datetimes = Gadget.GenerateEndDayofMonth(datetime1, datetime2)
    for dt in datetimes:
        print("ReBuild Sylted Portfolio @ " + str(dt))
        # --- 15:00:00 ---
        # localDateTime = Gadget.ToLocalDateTime(dt)
        # localDateTime = localDateTime + datetime.timedelta(days=-1)
        # dt = Gadget.ToClosingDateTime(localDateTime)
        # ---Rebuild Profile Data ( By specified DateTime)---
        smaValue = []
        bigValue = []
        smaNeutral = []
        bigNeutral = []
        smaGrowth = []
        bigGrowth = []

        # ---Filter Instruments (those Delisted)---
        instruments = Gadget.FindListedInstrument(database, dt)

        # ---Collect Data from Cache---
        factors = ['PriceBookLF', 'Cap']
        dfData = IO.LoadFactorsProfileAsDataFrame(database, dt, instruments, factors)
        # print(dfData)
        dateStr = Gadget.ToDateString(dt)
        dfData.to_csv(outputFolder + "dfData_" + dateStr + ".csv")
        #
        dfData = dfData.dropna()

        if len(dfData) == 0:
            print("No Data this Time")
            continue
        # print(dfData)
        dfData.to_csv(outputFolder + "dfData_Back_" + dateStr + ".csv")

        #
        print("ReBuild Sylted Portfolio @ " + str(dt) + " #Instruments " + str(len(instruments)) + " #Data " + str(len(dfData)))

        # ---Ranking---
        # ---记录关键分割点---
        rank70 = dfData.quantile(0.7)
        rank50 = dfData.quantile(0.5)
        rank30 = dfData.quantile(0.3)
        #
        cap50 = rank50["Cap"]

        # ---进行分组---
        # ---Classify Market Style ---
        for index, row in dfData.iterrows():
            pb = row["PriceBookLF"]
            cap = row["Cap"]
            symbol = row["Symbol"]
            # position = {}
            # position["Symbol"]

            # The BE/ME breakpoints are the 30th and 70th NYSE percentiles.
            if pb < rank30["PriceBookLF"]:
                if cap >= cap50:
                    bigValue.append({"Symbol":symbol})
                    # Label.AddLabel(database, symbol, "Label", "BigValue", dt)
                else:
                    smaValue.append({"Symbol":symbol})
                    # Label.AddLabel(database, symbol, "Label", "SmallValue", dt)
            #
            elif pb < rank70["PriceBookLF"]:
                if cap >= cap50:
                    bigNeutral.append({"Symbol":symbol})
                    # Label.AddLabel(database, symbol, "Label", "BigNeutral", dt)
                else:
                    smaNeutral.append({"Symbol":symbol})
                    # Label.AddLabel(database, symbol, "Label", "SmallNeutral", dt)
            #
            else:
                if cap >= cap50:
                    bigGrowth.append({"Symbol":symbol})
                    # Label.AddLabel(database, symbol, "Label", "BigGrowth", dt)
                else:
                    smaGrowth.append({"Symbol":symbol})
                    # Label.AddLabel(database, symbol, "Label", "SmallGrowth", dt)
        # ---
        # --- End of Loop DataFrame to Classify Market Style---

        #
        portfolios["SmaValue"].Rebalance(smaValue, dt)
        # portfolios["BigValue"].Rebalance(bigValue, dt)
        # portfolios["SmaNeutral"].Rebalance(smaNeutral, dt)
        # portfolios["BigNeutral"].Rebalance(bigNeutral, dt)
        # portfolios["SmaGrowth"].Rebalance(smaGrowth, dt)
        # portfolios["BigGrowth"].Rebalance(bigGrowth, dt)

        #
        SaveMarketStyledStockList(database, "SmaValue", smaValue, dt)
        SaveMarketStyledStockList(database, "BigValue", bigValue, dt)
        SaveMarketStyledStockList(database, "SmaNeutral", smaNeutral, dt)
        SaveMarketStyledStockList(database, "BigNeutral", bigNeutral, dt)
        SaveMarketStyledStockList(database, "SmaGrowth", smaGrowth, dt)
        SaveMarketStyledStockList(database, "BigGrowth", bigGrowth, dt)

        # --- Rebalence ---
        #portfolios["SmaValue"].Rebalance(smaValue, dt)
        #portfolios["BigValue"].Rebalance(bigValue, dt)
        #portfolios["SmaNeutral"].Rebalance(smaNeutral, dt)
        #portfolios["BigNeutral"].Rebalance(bigNeutral, dt)
        #portfolios["SmaGrowth"].Rebalance(smaGrowth, dt)
        #portfolios["BigGrowth"].Rebalance(bigGrowth, dt)

        #for name, portfolio in portfolios.items():
        #    portfolio.Rebalance(smaValue, dt)

        #
    # ---End of Loop DateTime---
    # testPortfolio = portfolios["SmaValue"]
    # testPortfolio.CalculateDaily(datetime1, datetime2)
    # testPortfolio.Save()

    #for name, portfolio in portfolios.items():
    #    portfolio.CalculateDaily(datetime1, datetime2)
    #    portfolio.Save()


def BuildFamaFrench3Model(database, datetime1, datetime2):

    outputFolder = "D:/Data/FamaFrench3-4/"

    portfolioNames = []
    portfolioNames.append("SmaValue")
    portfolioNames.append("BigValue")
    portfolioNames.append("SmaNeutral")
    portfolioNames.append("BigNeutral")
    portfolioNames.append("SmaGrowth")
    portfolioNames.append("BigGrowth")

    #
    dfFamaFrenchData = pd.DataFrame()
    portfolios = {}
    # --- Loop Portfolio ---
    for portfolioName in portfolioNames:
        #
        portfolioDataSeries = database.find("Misc", "MarketStyle", beginDateTime=datetime1, endDateTime=datetime2, query={"Name": portfolioName})
        portfolios[portfolioName] = pd.DataFrame()
        dfPortfolio = portfolios[portfolioName]

        # ---Loop DateTime---
        dtCount = 0
        while dtCount < len(portfolios): # Loop Counts by Length of Portfolios
            portfolio = portfolios[dtCount]
            #
            if dtCount == 0: # "RangeBegin" 只有第一次需要明确赋值，其余按照每次循环结束日期赋值
                rangeBegin = portfolio["StdDateTime"]  # last Day of month
            instruments = portfolio["InstrumentList"]
            #
            if dtCount >= len(portfolio):
                rangeEnd = datetime2
            else:
                rangeEnd = portfolios[dtCount + 1]["StdDateTime"]

            #
            print(portfolioName + " Load Range TimeSeries " + str(rangeBegin) + " " + str(rangeEnd))

            # --- A Period Portfolio Return ---
            df = PortfolioReturn(database, instruments, rangeBegin, rangeEnd, portfolioName)
            # first day is Zero
            df.drop(0, inplace=True)  # if index is Num
            # print(df)
            # Merge every Period
            if dfPortfolio.empty:
                dfPortfolio = df
            else:
                frames = [dfPortfolio, df]
                dfPortfolio = pd.concat(frames)
                # must REindexing
                dfPortfolio.reset_index(drop=True, inplace=True)
            # print(dfPortfolio)

            # Update BeginTime: portfolio dataframe last line
            dfLastDateTime = dfPortfolio.iloc[-1, 0]
            dateTimeObject = datetime.datetime(dfLastDateTime.year, dfLastDateTime.month, dfLastDateTime.day, \
                                               dfLastDateTime.hour, dfLastDateTime.minute, dfLastDateTime.second)
            rangeBegin = Gadget.ToUTCDateTime(dateTimeObject)

            #
            dtCount += 1


        #
        # dfPortfolio["UnitNetAssetValue"] = 1
        # dfPortfolio["UnitNetAssetValue"] = dfPortfolio["UnitNetAssetValue"].shift(1) * (1 + dfPortfolio["Average"])
        # final dataframe
        # print(dfPortfolio)
        portfolios[portfolioName] = dfPortfolio

        # ---End of a Portfolio---
        dfPortfolio.to_csv(outputFolder + portfolioName + ".csv")
        # dfFamaFrenchData[portfolioName + "_DateTime"] = dfPortfolio["DateTime"]
        dfFamaFrenchData[portfolioName] = dfPortfolio["Average"]

    # ---End 6 Portfolios---
    kkwood = 0
    # dfFamaFrenchData["DateTime2"] = portfolios["SmaValue"]["DateTime"]
    dfFamaFrenchData["SMB"] = 0.33333333 * (portfolios["SmaValue"]["Average"] + portfolios["SmaNeutral"]["Average"] + portfolios["SmaGrowth"]["Average"])\
                             -0.33333333 * (portfolios["BigValue"]["Average"] + portfolios["BigNeutral"]["Average"] + portfolios["BigGrowth"]["Average"])
    dfFamaFrenchData["HML"] = 0.5 * (portfolios["SmaValue"]["Average"] + portfolios["BigValue"]["Average"]) \
                             -0.5 * (portfolios["SmaGrowth"]["Average"] + portfolios["BigGrowth"]["Average"])


    #---Prepare Market Return---
    marketSymbol = "000300.SH"
    dfMarket = IO.LoadBarsAsDataFrame(database, marketSymbol, datetime1, datetime2, fields=["Close"], instrumentType="Index")
    dfMarket["Return"] = dfMarket["Close"] / dfMarket["Close"].shift(1) - 1
    dfMarket.drop(0, inplace=True)
    dfMarket.reset_index(drop=True, inplace=True)
    print(dfMarket)

    #---
    dfFamaFrenchData["Market"] = dfMarket["Return"]
    dfFamaFrenchData["DateTime"] = dfMarket["DateTime"]
    dfFamaFrenchData.to_csv(outputFolder + "FamaFrench" + ".csv")
    kkwood = 0


def RegressionToFamaFrench3Model():

    # ---Input: Equity Curve---


    # ---Market Return---
    marketRets = Algorithm.CalcLogRet(bmBarSeries, bmIndex, maxReferenceBars)
    xMarketRets = []
    for ret in marketRets:
        xMarketRets.append([ret])

    # ---SMB: Small - big Cap---


    # ---HML: High - Low BooktoMarket ---


    # ---回归模型---
    linreg = LinearRegression(copy_X=True, fit_intercept=True, n_jobs=1, normalize=False)
    model = linreg.fit(xMarketRets, yStockRets)
    marketAlpha = model.intercept_ * yStockRets.__len__()
    marketBeta = model.coef_[0]
    residuals = (linreg.predict(xMarketRets) - yStockRets)
    idioValotility = np.std(residuals) * math.sqrt(yStockRets.__len__())


def SaveMarketStyledStockList(database, name, list, tradeDateTime):

    doc = {}
    doc["Name"] = name
    doc["InstrumentList"] = list
    doc["StdDateTime"] = tradeDateTime
    localDateTime = Gadget.ToLocalDateTime(tradeDateTime)
    doc["DateTime"] = Gadget.ToDateTimeString(localDateTime)
    doc["Key"] = name + "_" + doc["DateTime"]
    database.upsert("Misc", "MarketStyle", {"Key": doc["Key"]}, doc)


def CalcMarketStyle2(database, instruments, datetime1, datetime2):
    print("Calc MarketStyle")
    outputFolder = "D:/Data/FamaFrench_MarketStyle2/"

    # --- Load Cap Factor ---

    # --- Load PB Factor ---

    # --- Load Close Price ---

    # --- Convert to Return ---

    # ---Loop Date---

    # ---Sort Row ---> Determin Market Style---

    # --- Fetch Return to Calculate Average Return ---

    # --- Save MarketStyle to DataBase ---


def MarketStyleBox(databse, positions, datatime2):
    datetime1 = datetime.datetime(2000,1,1)
    datetime1 = Gadget.ToUTCDateTime(datetime1)
    #
    portfolioNames = []
    portfolioNames.append("SmaValue")
    portfolioNames.append("SmaGrowth")
    portfolioNames.append("SmaNeutral")
    portfolioNames.append("BigValue")
    portfolioNames.append("BigGrowth")
    portfolioNames.append("BigNeutral")

    for name in portfolioNames:
        instruments = databse.find("Misc", "MarketStyle",datetime1,datatime2,query={"Name":name})
        instruments = instruments[len(instruments)-1] # last-one